﻿Set-StrictMode –Version Latest

function Copy-SCCMPackage {
<#
.SYNOPSIS

    Copies SCCM packages object(s)s to computer(s) installed with the SCCM distribution point role.

.DESCRIPTION

   The Copy-SCCMPackage cmdlet copies SCCM package objects to distribution points specified with the -Computername parameter.

   * Will only accept valid SCCM package objects that has the AddDistributionPoints() method.

   * Return packages that are copied successfully.

   * Will write errors for packages that fail to copy.
    
.PARAMETER ComputerName

    The computer(s) installed with the distribution point role to copy the package(s) to.

.PARAMETER Connection

    The connection configuration to connect to the SCCM provider. 
    
    This is obtained using the Get-SCCMConnection cmdlet.

    Validation will fail if object is not a valid connection configuration.

.PARAMETER Package

    The SCCM package object(s) to be copied to computer(s) installed with distribution point role. 

    This can be obtained using the Get-SCCMPackage cmdlet. 

    Validation will fail if object is not a valid SCCM package object.

.INPUTS

    System.Management.ManagementObject

.OUTPUTS

    System.Management.ManagementObject

.NOTES

    Author: Fadly Tabrani
    Webpage: http://pssccm.codeplex.com

.LINK

    MSDN - How to Assign a Package to a Distribution Point 
    http://msdn.microsoft.com/en-us/library/hh949578.aspx

.EXAMPLE

    Get-SCCMPackage -ID SIT0001D | Copy-SCCMPackage -ComputerName SMS-02

    Copies the package SIT0001D to the distribution point on the computer SMS-02
#>
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string[]]
        $ComputerName,

        # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,

        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [ValidateScript({ 
            
            # The package object must have the AddDistributionPoints method.
            $_.AddDistributionPoints     
            }
        )]
        [System.Management.ManagementObject[]]
        $Package
    )

    Begin {
        
        $ComputerName | ForEach-Object { 
        
            Write-Verbose -Message  "Getting distribution points: $_"
        }

        # Get the distribution points on the computer specified, including the pxe boot distribution points.
        $all_dps = $ComputerName | Get-SCCMSystemResource -RoleName DistributionPoint -Connection $Connection

        $all_dps | ForEach-Object { 

            Write-Verbose -Message "Found distribution point: $($_.NALPath)"
        }

        # Filter the non-boot distribution points.
        $nb_dps = $all_dps | Where-Object { $_.NALPath -notmatch 'SMSPXEIMAGES' }
    }

    Process {

        foreach ($pkg in $Package) {

            $copy_success = $true

            Write-Verbose -Message ("Processing package: {0}: {1}" -f $pkg.PackageID, $pkg.Name)

            # Boot images need to be distributed to all distribution points.
            if ($pkg.__CLASS -ieq 'SMS_BootImagePackage') { 
            
                $dps = $all_dps
            }

            # Normal packages need only to be distributed to normal distribution points.
            else {

                $dps = $nb_dps 
            }
            
            # We  can add all the distribution points all at oncebut we'll do it one at a time to check for errors. 
            foreach ($dp in $dps) {

                Write-Verbose -Message ("Copying package to NALPath: {0}" -f $dp.NALPath)

                $return_val = ($pkg.AddDistributionPoints(@($dp.SiteCode), @($dp.NALPath))).ReturnValue

                if ($return_val -ne 0) {

                    $copy_success = $false

                    Write-Error -Message  ("Copy failed: {0}: {1}" -f $pkg.PackageID, $dp.NALPath)
                }

                else {

                    Write-Verbose -Message ("Copy successful: {0}: {1}" -f $pkg.PackageID, $dp.NALPath)
                }
            }

            # Return the package if all copy operations was successful.
            if ($copy_success) { $pkg }
        }
    }  
}

function Get-SCCMConnection {
<#
.SYNOPSIS

    Resolves connection parameters to connect to a computer installed with the SCCM provider role.

.DESCRIPTION

    The Get-SCCMConnection resolves resolves connection parameters to be used to connect to an SCCM provider. 

    By default, will try to automatically get these parameters by checking the SCCM client's configuration. 
    
    * Auto-detection will not work on a computer without the SCCM client installed. 
    
    * The cmdlet will generate a stop error if the conneciton cannot be resolved.

    * The connection parameters will be returned in the form of a hastable.
    
.PARAMETER ComputerName

    The computer(s) installed with the SCCM provider role to connect to.

.PARAMETER Credential

    Connect to the provider using credentials. 
    
    This is obtained using the Get-Credential cmdlet.

.PARAMETER SetDefault

    Indicate that the connection configuration will be used as default throughout the session.

    The $SCCMDefaultConnection variable will be written globally.

.PARAMETER SiteCode

    The SCCM site code that the provider is installed.

    Validation will fail if this is not a valid three-letter site code.

.INPUTS

    System.String

.OUTPUTS

    System.Collections.Hashtable

.NOTES

    Author: Fadly Tabrani
    Webpage: http://pssccm.codeplex.com

.LINK

    MSDN - Calling Configuration Manager Code Snippets 
    http://msdn.microsoft.com/en-us/library/hh949053.aspx

.EXAMPLE 

    Get-SCCMConnection

    Auto-resolves the connection parameters. This must be executed on a computer installed with the SCCM client to work. 

.EXAMPLE

    Get-SCCMConnection -ComputerName SMS-01 -SiteCode S01 -SetDefault

    Connects to the SCCM provider on the computer SMS-01 with the site code S01 and use the connection parameter throughout the session.
#>
    [CmdletBinding(DefaultParametersetName="AutoConnect")]
    param (
        [Parameter(ParameterSetName='ManualConnect', Position=0)]
        [String] 
        $ComputerName = $env:COMPUTERNAME,

        [System.Management.Automation.PSCredential]
        $Credential,

        [switch]
        $SetDefault,

        [Parameter(Mandatory=$true, ParameterSetName='ManualConnect', Position=1)]
        [ValidateLength(3,3)]
        [String]
        $SiteCode
    )

    # By default, the function tries to resolve the computername and sitecode.
    if ($PSCmdlet.ParameterSetName -ieq 'AutoConnect') {

        # Get all the management points in the client's site, sorting by the last one that was updated.
        $params = @{ 
            
            Class = 'SMS_MPInformation' 
            Namespace = 'root\ccm\locationservices'
        }

        Write-Verbose -Message "Querying management point info from client location services"

        $mgmt_points = Get-WmiObject @params | Sort-Object -Property MPLastUpdateTime -Descending

        # We will try to connect to each management point to check if the provider role is installed.
        foreach ($mgmt_point in $mgmt_points) {

            Write-Verbose -Message ("Found managment point info: {0} : {1}" -f  $mgmt_point.SiteCode, $mgmt_point.MP)

            $params = @{
                    
                ComputerName = $mgmt_point.MP
                SiteCode = $mgmt_point.SiteCode
                Credential = $Credential
            } 
            
            try {

                $provider = Get-SCCMProvider @params -ErrorAction Stop
            }
            catch [System.Management.ManagementException]
            {
                if (-not $_.Exception.Message -match 'invalid namespace') { throw $_.Exception }
            }

            # Break the loop once a valid provider is found.
            if ($provider -and $provider.__CLASS -ieq 'SMS_ProviderLocation') { break }
        }
    }

    elseif ($PSCmdlet.ParameterSetName -ieq 'ManualConnect') {

        $params = @{ 
            
            ComputerName = $ComputerName
            SiteCode = $SiteCode
            Credential = $Credential
        } 

        Write-Verbose -Message ("Querying management point info: {0}: {1}" -f $params.ComputerName, $params.SiteCode)

        $provider = Get-SCCMProvider @params -ErrorAction Stop
    }

    if ($provider -and $provider.__CLASS -ieq 'SMS_ProviderLocation') { 

        Write-Verbose -Message "Found SCCM provider: $($provider.Machine)"

        $connection = @{ 
    
            ComputerName = $provider.Machine
            Namespace = "root\sms\site_$($provider.SiteCode)" 
        }

        # Add the credentials to the connection if specified and provider is not on local computer.
        if ($Credential -and $connection.ComputerName -ine $env:COMPUTERNAME) { $connection.Credential = $Credential }

        # Return the object.
        $connection
    }
    else {

        throw "Provider cannot be auto-resolved. Provide additional parameters to the Get-SCCMConnection cmdlet."
    }

    # Set the default connection if specified.
    if ($SetDefault) {

        Set-Variable -Name SCCMDefaultConnection -Value $connection -Scope Global -Force

        Write-Verbose ("Default connection set: {0}: {1}" -f $connection.Computername, $connection.Namespace)
    }
}

function Get-SCCMProvider {
<#
.SYNOPSIS

    Connects to a computer installed with the SCCM provider role.
    
.PARAMETER ComputerName

    The computer(s) installed with the SCCM provider role to connect to.

.PARAMETER Credential

    Connect to the provider using credentials. 
    
    This is obtained using the Get-Credential cmdlet.

.PARAMETER SiteCode

    The SCCM site code that the provider is installed.

    Validation will fail if this is not a valid three-letteres site code.

.INPUTS

    System.String

.OUTPUTS

    System.Management.ManagementObject

.NOTES

    Author: Fadly Tabrani
    Webpage: http://pssccm.codeplex.com

.LINK

    MSDN - Calling Configuration Manager Code Snippets 
    http://msdn.microsoft.com/en-us/library/hh949053.aspx

.EXAMPLE

    Get-SCCMProvider -ComputerName SMS-01 -SiteCode S01 -Credential (Get-Credential) 

    Connect to the SCCM provide on the computer SMS-01 withe the site code of S01 using a set of credentials. 
    
#>
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [string] 
        $ComputerName,

        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter(Mandatory=$true)]
        [ValidateLength(3,3)]
        [string]
        $SiteCode
    )

    Write-Verbose ("Querying provider location: {0}: {1}" -f $SiteCode, $ComputerName)

    $params = @{

        ComputerName = $ComputerName
        Namespace = 'root\sms'
        Class = 'SMS_ProviderLocation'
        Filter = "SiteCode='$SiteCode' AND ProviderForLocalSite=True"  
    }

    if ($Credential) {

        $params.Credential = $Credential
    }

    try {

        Get-WmiObject @params
    }
    catch [System.Management.ManagementException] {

        if ($_.Exception.ErrorCode.ToString() -ieq 'LocalCredentials') {

            $params.Remove('Credential')

            Get-WmiObject @params
        }

        else { throw $_.Exception }
    }
}

function Get-SCCMObject {
<#
.Synopsis

    Gets SCCM objects using WMI. 

.DESCRIPTION

    The Get-SCCMObject cmdlet retrieves objects from the SCCM namespace on computer. 

    These objects include but not limited to packages, ditribution points and programs. 

.PARAMETER Class 

    The object class to be retrieved from the SCCM namespace. 

    Same format as the -Class parameter on the Get-WmiObject cmdlet.

.PARAMETER Connection 

    The connection configuration to connect to the SCCM provider. 
    
    This is obtained using the Get-SCCMConnection cmdlet.

    Validation will fail if object is not a valid connection configuration.

.PARAMETER Filter

    Filter for WQL query.

    Same format as -Filter parameter on the Get-WMIObject cmdlet.

.PARAMETER Query

    Raw WQL query.
    
    Same format as -Query paramaeter on the Get-WMIObject cmdlet.
#>
    [CmdletBinding(DefaultParameterSetName='ByFilter')]
    Param(
        [Parameter(Mandatory=$true, ParameterSetName='ByFilter', Position=0)]
        [string]
        $Class,

        # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,
        
        [Parameter(ParameterSetName='ByFilter')]
        [string]
        $Filter,

        [Parameter(Mandatory=$true, ParameterSetName='ByQuery', Position=0)]
        [string]
        $Query
    )

    Write-Verbose -Message ('Querying WMI: {0}: {1}:' -f $Connection.Computername, $Connection.Namespace)

    switch ($PSCmdlet.ParameterSetName) {

        'ByFilter' { 
        
            Write-Verbose -Message ("Using class, filter: $Class`: $Filter")

            Get-WmiObject @Connection -Class $Class -Filter $Filter
            
            break 
        }

        'ByQuery' {

            Write-Verbose -Message ("Using query: $Query")

            Get-WmiObject @Connection -Query $Query
            
            break 
        }
    }
}

function Set-SCCMObject {
    [CmdletBinding()]
    Param(

        [hashtable]
        $Arguments=@{},
        
        [Parameter(Mandatory=$true)]
        [string]
        $Class,

         # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection
    )

    Set-WmiInstance @Connection -Class $Class -Arguments $Arguments
}

function ConvertTo-HashTable {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   Position=0)]
        $PSObject
    ) 
    $hash_table = @{}

    $PSObject.psobject.properties | Foreach { $hash_table[$_.Name] = $_.Value }

    $hash_table
}

function Export-SCCMPackagePreload {
<#
.SYNOPSIS

    Copies preload packages .PCK file to a directory and prepare batch file for the PreloadPkgOnSite.exe tool.

.DESCRIPTION

    The Export-SCCMPreloadPackage is used in conjunction with the PreloadPkgOnSite.exe tool. 

    It queries the distribution point specified in the -Computername parameter for these packages and copies them to a directory.

    A batch file is generated the in the export directory to be used for preloading the packages.

    Package objects from the Get-SCCMPackage cmdlet or PackageID can be piped into this cmdlet.

.PARAMETER  <Parameter-Name>

    The description of a parameter. You can include a Parameter keyword for
    each parameter in the function or script syntax.

    The Parameter keywords can appear in any order in the comment block, but
    the function or script syntax determines the order in which the parameters
    (and their descriptions) appear in Help topic. To change the order,
    change the syntax.
 
    You can also specify a parameter description by placing a comment in the
    function or script syntax immediately before the parameter variable name.
    If you use both a syntax comment and a Parameter keyword, the description
    associated with the Parameter keyword is used, and the syntax comment is
    ignored.

.EXAMPLE
    A sample command that uses the function or script, optionally followed
    by sample output and a description. Repeat this keyword for each example.

.INPUTS
   Inputs to this cmdlet (if any)

.OUTPUTS
    The .NET Framework type of the objects that the cmdlet returns. You can
    also include a description of the returned objects.

.NOTES
    1. Check package StoredPkgPath attribute to check if the package is found on the computer (true for primary site servers). 

    2. If the package is not found query SMS_PackageStatus and retrieve from the Location attribute.

    3. Compile information into an array and generate batch file and download to path specified.

.LINK
    The name of a related topic. Repeat this keyword for each related topic.

    This content appears in the Related Links section of the Help topic.

    The Link keyword content can also include a Uniform Resource Identifier
    (URI) to an online version of the same Help topic. The online version 
    opens when you use the Online parameter of Get-Help. The URI must begin
    with "http" or "https".

.COMPONENT
    The technology or feature that the function or script uses, or to which
    it is related. This content appears when the Get-Help command includes
    the Component parameter of Get-Help.
#>
    [CmdletBinding(DefaultParameterSetName='AllPackages')]
    Param (

        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,
       
        [Parameter(Mandatory=$true, ParameterSetName='ByPackage', ValueFromPipeline=$true)]
        [ValidateScript({ $_.__SUPERCLASS -ieq 'SMS_PackageBaseclass' })]
        [System.Management.ManagementObject[]]
        $Package,
        
        # The path of the directory where the PCK files, batch files and executable will be exported to.
        # Parameter is validated and will error out if directory does not exists.
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path -Path $_ -PathType Container })]
        [string]
        $Path,

        # The path of the PreloadPkgOnSite.exe file.
        # Defaults to the default installation path if not provided. 
        # Parameter is validated and error out if the file does not exist. 
        [string]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf } )]
        $ExecutablePath = 'C:\Program Files (x86)\ConfigMgr 2007 Toolkit V2\ServerTools\PreloadPkgOnSite.exe'
    )
 
    Begin {

        [string]$sub_filter = $null
        [psobject[]]$pkgs = $null

        $batch_content = "ECHO OFF`r`nCLS`r`n"      

        # Get all packages by default
        if ($PSCmdlet.ParameterSetName -ieq 'AllPackages') { $Package = Get-SCCMPackage -Connection $Connection }
    }

    Process {

        foreach ($pkg in $Package) {

            # Temporary placeholder to hold package information.
            $pkg | Add-Member -MemberType NoteProperty -Name CompressedFilePath -Value $null

            # Check if the information of the location of the PCK is available is in the package object.
            if ($pkg.StoredPkgPath -match "^\\\\$ComputerName\\.*?\.PCK$") {

                $pkg.CompressedFilePath = $pkg.StoredPkgPath
            }

            # Add the package to the array of records.
            $pkgs += $pkg | Select-Object Name, PackageID, StoredPkgVersion, CompressedFilePath
        }
    }

    End {

        foreach ($pkg in ($pkgs | Where-Object { -not $_.CompressedFilePath })) {

            $sub_filter = $sub_filter | Format-WQL -Operator OR -Expression "PackageID='$($pkg.PackageID)'"
        }

        if ($sub_filter) {

            $filter = "PkgServer='$ComputerName' AND Location LIKE '%.PCK'"
        
            $filter = $filter| Format-WQL -Operator AND -Expression $sub_filter -Parenthesize 

            $params = @{
             
                 Class = 'SMS_PackageStatus'
                 Connection = $Connection
                 Filter = $filter 
            }

            foreach ($pkg in (Get-SCCMObject @params)) {

                ($pkgs | Where-Object { $_.PackageID -ieq $pkg.PackageID }).CompressedFilePath = $pkg.Location
            }
        }

        foreach ($pkg in ($pkgs | Where-Object { $_.CompressedFilePath }) ) {

            Copy-Item -Path $pkg.CompressedFilePath -Destination $Path -Force

            $batch_content += "ECHO Preloading package: {0}: {1}`r`n" -f $pkg.PackageID, $pkg.Name
            $batch_content += "attrib.exe +R `"%~dp0{0}.PCK`"`r`n" -f $pkg.PackageID
            $batch_content += "`"%~dp0PreloadPkgOnSite.exe`" {0} /UpdateStoredPkgVersion {1}`r`n" -f  $pkg.PackageID, $pkg.StoredPkgVersion.ToString()
        }

        $batch_content += "PAUSE"

        # Write the batch file.
        Set-Content -Path (Join-Path -Path $Path -ChildPath preload-pkg-onsite.bat) -Value $batch_content -Encoding Ascii -Force

        # Copy the executable. Issue warning if file not found. 
        try {
           
            Copy-Item -Path $ExecutablePath -Destination $Path -Force -ErrorAction Stop
        }
        catch [System.Management.Automation.ItemNotFoundException] {

            Write-Warning "$_ PreloadPkgOnSite.exe needs to be manually copied to $Path"
        } 
    }
}

function Export-SCCMPackageSource {
<#
.SYNOPSIS

    Copies SCCM package source files to a directory.
    
.PARAMETER ComputerName

    The computer(distribution point) where the source files are going to be copied from.

.PARAMETER Connection

    The connection configuration to connect to the SCCM provider. 
    
    This is obtained using the Get-SCCMConnection cmdlet.

    Validation will fail if object is not a valid connection configuration.

.PARAMETER Package

    The SCCM package object(s) to be copied.

    This can be obtained using the Get-SCCMPackage cmdlet. 

    Validation will fail if object is not a valid SCCM package object.

.PARAMETER Path

    The path of the directory where the package source folder will be copied to.

    Validated will fail if directory does not exists.

.INPUTS

    System.Management.ManagementObject

.OUTPUTS

    System.Management.ManagementObject

.NOTES

    Author: Fadly Tabrani
    Webpage: http://pssccm.codeplex.com
#>
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,

        [Parameter(Mandatory=$true, ParameterSetName='ByPackage', ValueFromPipeline=$true)]
        [ValidateScript({ $_.__SUPERCLASS -ieq 'SMS_PackageBaseclass' })]
        [System.Management.ManagementObject[]]
        $Package,
        
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path -Path $_ -PathType Container })]
        [string]
        $Path
    )


    Begin {

        [string[]] $pkg_ids = $null
    }

    Process {

        foreach ($pkg in $Package ) {

            # Validate the pipeline input.
            if ($pkg.__SUPERCLASS -ine 'SMS_PackageBaseclass') { 

                throw "Invalid input object" 
            }

            elseif ($pkg.__CLASS -ieq 'SMS_TaskSequencePackage') {

                Write-Error -Message "Input object of class SMS_TaskSequencePackage will not yield results"
            }

            # Start processing input.            
            Write-Verbose -Message ("Compiling query for sourcefile location: {0}: {1}" -f $pkg.PackageID, $pkg.Name)

            $pkg_ids += $pkg.PackageID
        }
    }

    End {

        # Format the string array to be used for the IN SQL query.
        $tmp = "'" + [string]::Join("','", $pkg_ids) + "'"

        $wql = [string]::Join(' ',  
            "SELECT * From SMS_PackageStatus AS status INNER JOIN SMS_Program AS program",
            "ON status.PackageID = program.PackageID AND status.Location LIKE 'MSWNET:%\\\\$ComputerName\\%'",
            "AND status.PackageID IN ($tmp)"
        )

        [string[]] $copied_pkgs = $null

        foreach ($result in (Get-SCCMObject -Connection $Connection -Query $wql)) {

            if ($copied_pkgs -notcontains $result.status.PackageID) {

                if ($result.status.Location -match "^MSWNET:\[.*](.*)$") {

                    $source_path = $Matches[1]

                    Copy-Item -Path $source_path -Destination $Path -Recurse -Force -Verbose:$VerbosePreference

                    $copied_pkgs += $result.status.PackageID
                }
            }

            $file_path = "$Path\{0}\_{1}.bat" -f $result.program.PackageID, $result.program.ProgramName

            $content = "ECHO OFF`r`nCLS`r`n{0}`r`nPAUSE" -f $result.program.CommandLine 

            $content | Set-Content -Path (New-Item -Path $file_path -ItemType File -Force) -Encoding Ascii 
        }

        # Return the packages that were successfully copied.
        if ($copied_pkgs) { $copied_pkgs | Get-SCCMPackage -Connection $Connection }
     }
}

function Get-SCCMPackage {
<#
.Synopsis
   Gets SMS_PackageBaseClass from the SCCM namespace.
.DESCRIPTION
   The Get-SCCMPackage is a helper cmdlet that retrieves SMS_PackageBaseClass objects using the Get-SCCMObject cmdlet.
#>
    [CmdletBinding(DefaultParametersetName="ByPackageID")]
    Param
    (
        # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,

        # Searches the namespace by PackageID property if specified.
        [Parameter(ParameterSetName='ByPackageID', Position=0, ValueFromPipeline=$True)]
        [ValidateLength(8, 8)]
        [Alias("PackageID")] 
        [string[]]
        $ID,

        # Searches the namespace by PackageName property if specified.
        [Parameter(ParameterSetName='ByName',Position=0)]
        [Alias("PackageName")]
        [string]
        $Name,

        # Helper filter to limit by the type of packages by PackageType.
        [string]
        [ValidateSet('Regular', 'Driver', 'TaskSequence', 'SoftwareUpdate', 'DeviceSetting', 'Image', 'BootImage', 'OperatingSystem')]
        [Alias("PackageType")]
        $Type
    )

    Begin {

        [string]$filter = $null
        [string]$subfilter = $null
    
        # Process the Name filter.
        if ($Name) { $filter = $filter | Format-WQL -Operator AND -Expression "Name LIKE '%$Name%'" }

        # Process the PackageType filter.
        $package_type = switch ($Type) {

            $null { break }
            'Regular' { 0; break }
            'Driver' { 3; break  }
            'TaskSequence' { 4; break }
            'SoftwareUpdate' { 5; break }
            'DeviceSetting' { 6; break }
            'Image' { 257; break }
            'BootImage' { 258; break }
            'OperatingSystem' { 259; break }
        }

        if ($package_type) { $filter = $filter | Format-WQL -Operator AND -Expression "PackageType=$package_type"}

        [string[]] $pkg_ids = $null
    }

    Process {

        # Process the PackageID filter. Compile into a WQL OR statement. 
        foreach ($package_id in $ID) {

            $subfilter = $subfilter | Format-WQL -Operator OR -Expression "PackageID='$package_id'"


       }
    }

    End { 
        
        # Use the PackagID filter compiled earlier and parenthesize it. 
        if ($subfilter) { $filter = $filter | Format-WQL -Operator AND -Expression $subfilter -SubFilter }

        $params = @{ 
        
            Connection = $Connection
            Class = 'SMS_PackageBaseClass'
            Filter = $filter 
        }

        Get-SCCMObject @params
    }
}  

function Get-SCCMSystemResource {
    [CmdletBinding()]
    Param(

        [Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [string[]]
        $ComputerName,

        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,

        # Helper filter to limit results by RoleName.
        [Parameter()]
        [ValidateSet(
            'ComponentServer',
            'DistributionPoint',
            'SiteServer',
            'SiteSystem',
            'SQLServer',
            'FallbackStatusPoint',
            'ReportingPoint',
            'SoftwareUpdatePoint',
            'PXEServicePoint',
            'ManagementPoint',
            'ServerLocatorPoint',
            'SRSReportingPoint'
        )]
        [string]
        $RoleName
    )
    Begin {

        [string]$filter = $null
        [string]$sub_filter = $null

        $role_name = switch ($RoleName) {

            $null { break }
            'ComponentServer' { 'Component Server'; break }
            'DistributionPoint' { 'Distribution Point'; break }
            'SiteServer' { 'Site Server'; break }
            'SiteSystem' { 'Site System'; break }
            'SQLServer' { 'SQL Server'; break }
            'FallbackStatusPoint' { 'Fallback Status Point'; break }
            'ReportingPoint' { 'Reporting Point'; break }
            'SoftwareUpdatePoint' { 'Software Update Point'; break }
            'PXEServicePoint' { 'PXE Service Point'; break }
            'ManagementPoint' { 'Management Point'; break }
            'ServerLocatorPoint' { 'Server Locator Point'; break }
            'SRSReportingPoint' { 'SRS Reporting Point'; break }
        }

        if ($role_name) { $filter = $filter | Format-WQL -Operator AND -Expression "Rolename='SMS $role_name'" }

    }

    Process {

        foreach ($computer_name in $ComputerName) {

            $sub_filter = $sub_filter | Format-WQL -Operator OR -Expression "ServerName='$computer_name'"
        }
    }

    End {
        
        if ($sub_filter) { $filter = $filter | Format-WQL -Operator AND -Expression $sub_filter -Parenthesize }

        Get-SCCMObject -Class 'SMS_SystemResourceList' -Connection $Connection -Filter $filter 
    }
}

function Get-SCCMTaskSequenceReferencePackage {
    [CmdletBinding()]
    Param (
    
        # Connection object obtained by using the Get-SCCMConnection cmdlet.
        # Try to use the default connection if not specified.
        [ValidateScript({ _ValidateConnection($_) })]
        [hashtable]
        $Connection = $SCCMDefaultConnection,

        # Accept pipeline input, object must be a task sequence package.
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateScript({ $_.__Class -ieq 'SMS_TaskSequencePackage' })]
        [System.Management.ManagementObject[]]
        $Package

    )

    Begin {

        [string]$filter = $null
    }

    Process {

        # Build the filter for the WQL query.
        foreach ($ts_package in $Package) {

            $filter = $filter | Format-WQL -Operator OR -Expression "PackageID='$($ts_package.PackageID)'"
        }
    }

    End {

        $params = @{ 
        
            Connection = $Connection
            Class = 'SMS_TaskSequenceReferencesInfo'
            Filter = $filter 
        }

        # Get unique ReferencePackageID objects and return its associated packages.
        Get-SCCMObject @params | Select-Object -ExpandProperty ReferencePackageID -Unique | Get-SCCMPackage -Connection $Connection
    }
}

function Format-WQL {
    [CmdletBinding(DefaultParametersetName="Escape")]
    Param (
    
    [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
    [AllowEmptyString()]
    [AllowNull()]
    [string]
    $WQL,
    
    [Parameter(Mandatory=$true, ParameterSetName='Operate')]
    [ValidateSet('AND', 'OR')]
    [string]
    $Operator,

    [Parameter(Mandatory=$true, ParameterSetName='Operate')]
    [string]
    $Expression,

    [Parameter()]
    [Alias("SubFilter")] 
    [switch]
    $Parenthesize
    )

    Process {

        if ($PSCmdlet.ParameterSetName -ieq 'Operate') {

            # Onlyt add an operator if the string was not empty.
            if ($WQL) { $WQL += " $Operator " }

            # Parenthesize the expression if it the subfilter parameter is specified.
            if ($WQL -and $Parenthesize) { $Expression = "($Expression)" }
     
            $WQL += $Expression
        }
        else {
        
             # Default action is to escape special characters.
            if ($WQL) { $WQL = $WQL.Replace('\','\\') }
        }

        $WQL
    }
}

function _ValidateConnection($Connection) {

    $validate = $true

    foreach ($key in $Connection.Keys) {

        if (-not $Connection[$key]) {

            throw "Invalid connection"

            $validate = $false

            break
        }
    }

    $validate
}